package com.leonzhangxf.spel.reference;

import lombok.Data;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.expression.AccessException;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.StringUtils;

/**
 * Bean References
 * <p>
 * If the evaluation context has been configured with a bean resolver,
 * you can look up beans from an expression by using the @ symbol.
 * <p>
 * To access a factory bean itself, you should instead prefix the bean name with an & symbol.
 *
 * @author leonzhangxf
 * @date 20190427
 */
public class BeanReferences {

    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setBeanResolver(new TeacherBeanResolver());

        // This will end up calling resolve(context,"teacher") on TeacherBeanResolver during evaluation
        Teacher teacher = parser.parseExpression("@teacher").getValue(context, Teacher.class);
        if (null != teacher) {
            teacher.setName("Leon");
            System.out.println(teacher);
        }

        // This will end up calling resolve(context,"&foo") on TeacherBeanResolver during evaluation
        TeacherFactory teacherFactory = parser.parseExpression("&teacher")
            .getValue(context, TeacherFactory.class);
        if (null != teacherFactory) {
            Teacher instance = teacherFactory.getInstance();
            instance.setName("Maggie");
            System.out.println(instance);
        }
    }

    public static class TeacherBeanResolver implements BeanResolver {

        private static final String TEACHER_BEAN_NAME = "teacher";

        @Override
        public Object resolve(EvaluationContext context, String beanName) throws AccessException {
            if (!StringUtils.hasText(beanName)) {
                return null;
            }

            if (!beanName.trim().endsWith(TEACHER_BEAN_NAME)) {
                throw new AccessException("Can not get the other bean expect teacher or teacher factory.");
            }

            if (beanName.trim().startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
                return new TeacherFactory();
            }

            return new Teacher();
        }
    }

    @Data
    private static class Teacher {

        private String name;
    }

    public static class TeacherFactory {

        Teacher getInstance() {
            return new Teacher();
        }
    }
}
